﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Threading;
using JigLibX.Physics;
using JigLibX.Collision;
using JigLibX.Geometry;
using Microsoft.Xna.Framework.Input.Touch;
using Services;
using Utilities;

namespace SpaceBattle.Logic.SampleLevel
{

  using Geometry = Body;
  using Game = Microsoft.Xna.Framework.Game;

  using Nuke = Projectile;
  using NukeData = ProjectileData;

  public abstract class BossLayer : BaseLevel
  {
    static protected readonly float ENEMY_MAX_SPEED = 1.0f;

    public BossLayer(Game game, BaseLevelStats current_score, int num_lives)
      : base(game, current_score, num_lives)
    {
    }

    ConfigurationService ships_config;
    public override void Initialize()
    {
      ships_config = Game.Services.GetService(typeof(ConfigurationService)) as ConfigurationService;
      base.Initialize();
    }

    /// <summary>
    /// Per far esplodere una nave dobbiamo:
    /// * toglierla dalla fisica
    /// * aggiungere l'esplosione
    /// </summary>
    protected void ExplodeShipAt(int index)
    {
#warning timer e punteggio andrebbero in un decoratore
      var p = CurrentState.bosses[index];
      physics.RemoveBody(p.geometry);
      NewExplosions.Add(new Explosion(true)
      {
        explosion_type = ExplosionType.Ship,
        originating_ship = p.OwnValue,
        position = p.geometry.Position,
      });

      audio_service.OnNukeExplode();

      if (p.ship_description.ShockWaveOnExplode)
      {
        ShootShockWave(p.geometry.Position,
            p.faction,
            p.ship_description.ShockWaveData.initialColor,
            p.ship_description.ShockWaveData.finalColor,
            p.geometry.Mass / 500,
            p.ship_description.ShockWaveData.speed,
            p.ship_description.ShockWaveData.damage,
            p
        );
        audio_service.OnShockwaveShoot();
      }
    }

    protected override Ship CreateBoss(Vector3 position, BoundingSphere bounding_sphere, float mass, ShipType ship_type, Faction faction, int NumNukes)
    {
      var sub_volumes = scene_service[ship_type].VolumesSeq;

      var new_boss = new Ship(true) // invochiamo il costruttore che prende un booleano (ignorato, basta non chiamare il costruttore di default)
      {
        stats = new Stats() { CurrentLife = 500.0f, MaxLife = 500.0f },
        geometry = PhysicsHelper.create_spheres(position, bounding_sphere, sub_volumes, mass),
        engines = new Engine[] { },
        faction = faction,
        ship_type = ship_type,
        ship_description = ships_config[ship_type]
      };
      new_boss.AdjustLifeByLevel();

      new_boss.geometry.ExternalData = new_boss;

      if (faction == Faction.Player)
      {
        new_boss.engines = new[]{
          new Engine(true)
          {
            Location = Vector3.Backward * 1.1f - Vector3.Right * 0.2f,
            Throttle = 0.0f
          },
          new Engine(true)
          {
            Location = Vector3.Backward * 1.1f + Vector3.Right * 0.1f,
            Throttle = 0.0f
          }
        };
      }
      else
      {
        new_boss.engines = new[]{
          new Engine(true)
          {
            Location = Vector3.Backward * 1.1f,
            Throttle = 0.0f
          }
        };
      }

      return new_boss;
    }

    float laser_volley_spawn_time = 4.0f, nuke_volley_spawn_time = 6.0f;
    float time_since_last_laser_volley_shot;
    float time_since_last_nuke_volley_shot;
    ArrayList<Body> next_others = new ArrayList<Geometry>(25);
    protected override void UpdateBoss(float dt)
    {
      // Aggiorniamo i boss (sparo e movimento)
      for (int i = 0; i < CurrentState.bosses.Count; i++)
      {
        var b = CurrentState.bosses[i];

        next_others.Clear();
        for (int j = i + 1; j < CurrentState.bosses.Count; j++)
        {
          next_others.Add(CurrentState.bosses[j].geometry);
        }
        b.geometry.RotateAround(CurrentState.player.geometry, dt, 1.0f, 2.5f, 7.5f, next_others);
        b.Update(dt, CurrentState.bosses[i].ship_description.Speed);


        b.stats = new Stats() { MaxLife = b.stats.MaxLife, CurrentLife = b.stats.CurrentLife - b.foldEnemies(DamageF, 0.0f) - ShockWaveDamage(b.geometry.Position, b.faction) };
        if (b.stats.CurrentLife <= 0.0f)
        {
          ExplodeShipAt(i);
          OnShipDestroyed(b);
          CurrentState.RemoveBossAt(i);
          i--;
          continue;
        }

        b.time_since_last_primary_shot += dt;
        b.time_since_last_secondary_shot += dt;
        time_since_last_laser_volley_shot += dt;
        time_since_last_nuke_volley_shot += dt;

        /// Se il nemico non spara da un po', lo facciamo sparare. Bisogna anche decidere che tipo di colpo sparare
        if (time_since_last_nuke_volley_shot > nuke_volley_spawn_time && r.NextDouble() < dt * 0.5)
        {
          time_since_last_nuke_volley_shot = 0;
          time_since_last_laser_volley_shot = Math.Max(0.0f, time_since_last_laser_volley_shot - 2.0f);
          ShotNukeVolley(b);
        }
        else if (time_since_last_laser_volley_shot > laser_volley_spawn_time && r.NextDouble() < dt * 0.5)
        {
          time_since_last_laser_volley_shot = 0;
          time_since_last_nuke_volley_shot = Math.Max(0.0f, time_since_last_nuke_volley_shot - 2.0f);
          ShotLaserVolley(b);
        }
        else if (b.time_since_last_primary_shot > b.ship_description.PrimaryShotSpawnTime && r.NextDouble() < dt * 0.5)
        {
          b.time_since_last_primary_shot = 0.0f;
          //ShootPrimaryWeapon(b, CurrentState.player.AsShip, new Nuke(), new Bonus(), TargetType.Ship, Vector3.Zero, Vector3.Zero);
          ShootWeapon(b.ship_description.PrimaryWeapons[0].Type, b, CurrentState.player.AsShip, Vector3.Zero, Vector3.Zero, new Nuke(), new Bonus(), TargetType.Ship);
        }
        // Non aggiungere il controllo sull'energia
        else if (b.time_since_last_secondary_shot > b.ship_description.SecondaryShotSpawnTime && r.NextDouble() < dt * 0.05)
        {
          b.time_since_last_secondary_shot = 0.0f;
          //ShootSecondaryWeapon(b, CurrentState.player.AsShip, Vector3.Zero, Vector3.Zero);
          ShootWeapon(b.ship_description.SecondaryWeapons[0].Type, b, CurrentState.player.AsShip, Vector3.Zero, Vector3.Zero, new Nuke(), new Bonus(), TargetType.Ship);
        }

        CurrentState.bosses[i] = b;
      }
    }

    private void ShotNukeVolley(Ship b)
    {
      var p = b.geometry.Position;
      var o = b.geometry.Orientation;
      var radius = b.geometry.bounding_sphere().Radius / 3.0f;
      var ull = Vector3.Normalize(Vector3.Lerp(o.Left, o.Forward, 0.5f));
      var url = Vector3.Normalize(Vector3.Lerp(o.Right, o.Forward, 0.5f));

      ShootWeapon(WeaponType.Nuke, b, CurrentState.player.AsShip, p + o.Forward * radius, o.Forward, new Nuke(), new Bonus(), TargetType.None);
      ShootWeapon(WeaponType.Nuke, b, CurrentState.player.AsShip, p + o.Left * radius, Vector3.Lerp(o.Left, o.Forward, 0.6f), new Nuke(), new Bonus(), TargetType.None);
      ShootWeapon(WeaponType.Nuke, b, CurrentState.player.AsShip, p + o.Right * radius, Vector3.Lerp(o.Right, o.Forward, 0.6f), new Nuke(), new Bonus(), TargetType.None);
      ShootWeapon(WeaponType.Nuke, b, CurrentState.player.AsShip, p + ull * radius, Vector3.Lerp(ull, o.Forward, 0.4f), new Nuke(), new Bonus(), TargetType.None);
      ShootWeapon(WeaponType.Nuke, b, CurrentState.player.AsShip, p + url * radius, Vector3.Lerp(url, o.Forward, 0.4f), new Nuke(), new Bonus(), TargetType.None);

    }

    private void ShotLaserVolley(Ship b)
    {
      var p = b.geometry.Position;
      var orientation = b.geometry.Orientation;
      var radius = b.geometry.bounding_sphere().Radius / 2;
      var ull = Vector3.Normalize(Vector3.Lerp(orientation.Left, orientation.Forward, 0.33f));
      var ull2 = Vector3.Normalize(Vector3.Lerp(orientation.Left, orientation.Forward, 0.66f));
      var url = Vector3.Normalize(Vector3.Lerp(orientation.Right, orientation.Forward, 0.33f));
      var url2 = Vector3.Normalize(Vector3.Lerp(orientation.Right, orientation.Forward, 0.66f));

      ShootWeapon(WeaponType.Double_Plasma, b, CurrentState.player.AsShip, p + orientation.Forward * radius, orientation.Forward, new Nuke(), new Bonus(), TargetType.None);
      ShootWeapon(WeaponType.Double_Plasma, b, CurrentState.player.AsShip, p + orientation.Left * radius, orientation.Left, new Nuke(), new Bonus(), TargetType.None);
      ShootWeapon(WeaponType.Double_Plasma, b, CurrentState.player.AsShip, p + orientation.Right * radius, orientation.Right, new Nuke(), new Bonus(), TargetType.None);
      ShootWeapon(WeaponType.Double_Plasma, b, CurrentState.player.AsShip, p + url * radius, url, new Nuke(), new Bonus(), TargetType.None);
      ShootWeapon(WeaponType.Double_Plasma, b, CurrentState.player.AsShip, p + ull * radius, ull, new Nuke(), new Bonus(), TargetType.None);
      ShootWeapon(WeaponType.Double_Plasma, b, CurrentState.player.AsShip, p + url2 * radius, url2, new Nuke(), new Bonus(), TargetType.None);
      ShootWeapon(WeaponType.Double_Plasma, b, CurrentState.player.AsShip, p + ull2 * radius, ull2, new Nuke(), new Bonus(), TargetType.None);
    }

    protected abstract void OnShipDestroyed(Ship ship);
    //protected abstract void ShootNuke(Ship shooter, Ship target);
    //protected abstract void ShootSecondaryWeapon(Ship shooter, Ship target, Vector3 position, Vector3 direction);
    //protected abstract void ShootLaser(Ship shooter, Ship target_ship, Nuke target_nuke, Bonus target_bonus, TargetType target_type);
    //protected abstract void ShootPrimaryWeapon(Ship shooter, Ship target_ship, Nuke target_nuke, Bonus target_bonus, TargetType target_type, Vector3 spawn_position, Vector3 direction);

    //protected abstract void ShootWeapon(WeaponType weapon_type, Ship shooter, Vector3 spawn_position, Vector3 direction, Ship target_ship, Nuke target_nuke, Bonus target_bonus, TargetType target_type);

    protected abstract void ShootWeapon(WeaponType weapon_type, Ship shooter, Ship target, Vector3 position, Vector3 direction, Nuke target_nuke, Bonus target_bonus, TargetType target_type);

    /// <summary>
    /// Calcola il danno subito in relazione ad una serie di entitá in collisione:
    /// ogni entitá causa un danno differente
    /// </summary>
    //protected abstract float Damage(IEnumerable<Body> colliders,Vector3 position, Faction faction));

    protected abstract Func<Body, float, float> DamageF { get; }
    //protected abstract float Damage(IEnumerable<Body> colliders, Vector3 position, Faction faction);
    protected abstract float ShockWaveDamage(Vector3 position, Faction faction);


    protected abstract void ShootShockWave(Vector3 position, Faction faction, Color initialColor, Color finalColor, float range, float speed, float damage, Ship shooter);
  }
}
